﻿using System.Windows.Controls;
using snat.model;
using System;
using GraphSharp.Algorithms.Layout.Simple.FDP;
using snat.algorithms;
using System.Collections.Generic;
using System.IO;
using System.Windows;
using QuickGraph.Serialization;
using System.Xml;
using QuickGraph;
using System.Windows.Threading;

namespace gui {
    /// <summary>
    /// Interaction logic for Graph.xaml
    /// This is the graph UI element of the Visualisation Tab
    /// </summary>
    public partial class Graph : UserControl {

        public static event guiGraphNodeSelectedEvent GraphNodeSelectedEvent;
        public static event guiOnFinishedProcessingEvent OnFinishedProcessingEvent;
        public static event pcRunning RunningEvent;
        public static event pcReady ReadyEvent;

        private NetworkViewModel viewModel = NetworkViewModel.Instance;
        public NetworkViewModel ViewModel {
            get { return viewModel; }
        }

        // Used this to override the graph layout parameters
        private ISOMLayoutParameters layoutParams = new ISOMLayoutParameters();
        public ISOMLayoutParameters LayoutParams {
            get { return layoutParams; }
            set { layoutParams = value; }
        }

        public Graph() {

            // Add Network Model event listeners to the graph
            NetworkModel.Instance.EdgeAdded += NetworkModelConnect.Instance.AddEdge;
            NetworkModel.Instance.NodeAdded += NetworkModelConnect.Instance.AddVertex;
            NetworkModelConnect.Instance.OnEdgeAdded += NetworkViewModel.Instance.AddEdge;
            NetworkModelConnect.Instance.OnNodeAdded += NetworkViewModel.Instance.AddVertex;
            NetworkModelConnect.Instance.OnEdgeRemoved += NetworkViewModel.Instance.RemoveEdge;
            NetworkModelConnect.Instance.OnNodeRemoved += NetworkViewModel.Instance.RemoveVertex;

            // Add GUI event listeners to the graph
            PlayControl.FreezeGraphEvent += FreezeLayout;
            NetworkModelConnect.FreezeGraphEvent += FreezeLayout;
            GraphicsTab.RelayoutEvent += Relayout;
            NetworkViewModel.RelayoutEvent += Relayout;
            NetworkModelConnect.RelayoutEvent += Relayout;
            GraphicsTab.CompactLayoutEvent += CompactLayout;           
            GraphicsTab.ExpandLayoutEvent += ExpandLayout;
            Menu.OnSaveJPGEvent += SaveJPG;

            // Connect to graph and initialise
            DataContext = ViewModel;
            InitializeComponent();
            GraphProgress.Visibility = Visibility.Hidden;
        }

        public void Relayout() {
            if (LayoutParams.MaxEpoch != 300 && LayoutParams.MaxEpoch != 1 && LayoutParams.MaxEpoch != 0) {
                LayoutParams.MaxEpoch = 300; // To apply clustering
            }
            RenderGraph();
        }

        // Performance render method designed to call a low priority thread to render the new chart
        private readonly object _objLock = new object();
        private void RenderGraph() {
            // Display loading spinner
            if (RunningEvent != null) RunningEvent();

            // Display the loading bar
            Random random = new Random();
            int rand = random.Next(40);
            lock (_objLock) {
                this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
                    new Action(delegate() {
                    GraphProgress.Value = 40 + rand;
                    GraphProgress.Visibility = Visibility.Visible;
                }));
            }

            // Render graph
            lock (_objLock) {
                DispatcherOperation operation =
                    this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background,
                    new Action(delegate() {
                        PerformRelayout();
                }
                ));
                operation.Completed += DispatcherOperation_Completed;
            }
        }
        // Hide loading bar once rendering has completed
        private void DispatcherOperation_Completed(object sender, EventArgs e) {
            this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
                new Action(delegate() {
                GraphProgress.Visibility = Visibility.Hidden;
            }));
            // Set spinner and status to ready
            if (ReadyEvent != null) ReadyEvent();
            StatusInfo.SetStatus("Ready");
            if (OnFinishedProcessingEvent != null) OnFinishedProcessingEvent(); // Close the LoadingBox
        }
        // Do the actual relayout
        private void PerformRelayout() {
            lock (_objLock) {
                graphLayout.Relayout();
            }
        }



        public void FreezeLayout() {
            LayoutParams.MaxEpoch = 0;  // To ensure new edges added do not cause the graph to relayout (massive speedup!)
        }

        public void CompactLayout() {
            LayoutParams.MaxEpoch = 1;
        }

        public void ExpandLayout() {
            LayoutParams.MaxEpoch = 300;
        }

        public ClusterNode nodeSelected;
        private void graphLayout_MouseMove(object sender, System.Windows.Input.MouseEventArgs e) {
            foreach (ClusterNode n in graphLayout.HighlightedVertices) {
                if (nodeSelected == n) return;
                else {
                    nodeSelected = n;
                    if (GraphNodeSelectedEvent != null) GraphNodeSelectedEvent(nodeSelected);
                    return;
                }
            }
        }

        #region ********** GRAPH OUTPUT **********

        // Save out jpeg screenshot to the given filename
        public void SaveJPG(string filename) {
            if (NetworkModelConnect.Instance.RunWithVisuals && graphLayout.RenderSize.Height > 0 
                                                            && graphLayout.RenderSize.Width > 0) {

                byte[] screenshot = graphLayout.GetJpgImage(1, 100);   // 1x scale, maximum quality
                FileStream fileStream = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite);
                BinaryWriter binaryWriter = new BinaryWriter(fileStream);
                binaryWriter.Write(screenshot);
                binaryWriter.Close();
            } else {
                MessageBox.Show("There is currently no graph information to save as a JPEG. Please select the Run With Visualisation checkbox on the Graphics tab in order to export graph screenshots.", "Cannot Save Graph Screenshot", MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }

        // Save out GraphML of Network Model
        public static void SaveGML(string filename) {
            if (NetworkModel.Instance.Nodes.Count > 0) {
                var g = new BidirectionalGraph<object, IEdge<object>>();
                g.AddVertexRange(NetworkModel.Instance.Nodes.Values);
                g.AddEdgeRange(NetworkModel.Instance.Edges);

                using (var writer = XmlWriter.Create(filename)) {
                    var serializer = new GraphMLSerializer<object, IEdge<object>, IMutableBidirectionalGraph<object, IEdge<object>>>();
                    serializer.Serialize(writer, g, v => (v as BaseNode).ToString(), e => (e as Edge).ToString());
                }
            } else {
                MessageBox.Show("There is currently no graph information to save as GraphML (GML). Please initialise a Network Model before attempt to save.", "Cannot Save GraphML", MessageBoxButton.OK, MessageBoxImage.Warning);
            }
        }
        #endregion

    }
}
